#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <resolv.h>
#include <arpa/inet.h>
#include <netdb.h>

#define SLOG(level,format,args...) \
	do{printf("[%s][%s:%d]",#level,__func__,__LINE__);printf(format,##args);}while(0)

#define SMTP_SEND(sock,buf,len,fmt,args...) \
	do{ \
		memset(buf,0,len); \
		snprintf(buf,len,fmt,##args); \
		if(send(sock,buf,strlen(buf),MSG_NOSIGNAL)<0) \
		{ \
			SLOG(ERR,"send fail!\n"); \
			ret = -1; \
			goto end_sendemail; \
		} \
	}while(0)

#define SMTP_RECV(sock,buf,len) \
	do{ \
		memset(buf,0,len); \
		if(recv(sock,buf,len,0)<=0) \
		{ \
			SLOG(ERR,"recv error!\n"); \
			ret = -1; \
			goto end_sendemail; \
		} \
		printf("%s\n",buf); \
	}while(0)

struct mail_meta
{
	char smtp_server[64];
	unsigned int smtp_port;
	char from[64];
	char code[64];
	char to[64];
	char subject[512];
	char info[1024];
	char attach[128];
	char *body; /* MIME body,malloced */
};

struct data6
{
	unsigned int d4:6;
	unsigned int d3:6;
	unsigned int d2:6;
	unsigned int d1:6;
};

static char con628(char c6)
{
	char rtn = '\0';
	if (c6 < 26) rtn = c6 + 65; /*[index]~ A-Z */
	else if (c6 < 52) rtn = c6 + 71;/* [index] ~ a-z,71=97-26 */
	else if (c6 < 62) rtn = c6 - 4; /* index:52-61 ~ '0'-'9',0:48 */
	else if (c6 == 62) rtn = 43;/* 62 ~ '+' */
	else rtn = 47; /* 63 ~ '/' */
	return rtn;
}

// base64的实现
/* For compatible both GCC and ARM-hisiv100-linux-gcc */
static void base64(char *dbuf, char *sbuf, int len)
{
	int i = 0;
	char *tmp = NULL;

	for(i=1;i<=len/3;i++)
	{
		tmp = sbuf + (i-1)*3;
		dbuf[(i-1)*4+0] = con628((tmp[0]>>2)&0x3f);
		dbuf[(i-1)*4+1] = con628((((tmp[0]&0x03)<<4)|((tmp[1]>>4)&0x0f))&0x3f);
		dbuf[(i-1)*4+2] = con628((((tmp[1]&0x0f)<<2)|((tmp[2]>>6)&0x03))&0x3f);
		dbuf[(i-1)*4+3] = con628(tmp[2]&0x3f);
	}
	if(len%3 == 1)
	{
		tmp = sbuf + (i-1)*3;
		dbuf[(i-1)*4+0] = con628((tmp[0]>>2)&0x3f);
		dbuf[(i-1)*4+1] = con628(((tmp[0]&0x03)<<4)&0x3f);
		dbuf[(i-1)*4+2] = '=';
		dbuf[(i-1)*4+3] = '=';
	}
	else if(len%3 == 2)
	{
		tmp = sbuf + (i-1)*3;
		dbuf[(i-1)*4+0] = con628((tmp[0]>>2)&0x3f);
		dbuf[(i-1)*4+1] = con628((((tmp[0]&0x03)<<4)|((tmp[1]>>4)&0x0f))&0x3f);
		dbuf[(i-1)*4+2] = con628(((tmp[1]&0x0f)<<2)&0x3f);
		dbuf[(i-1)*4+3] = '=';
	}
	return ;
}

/** Available for GCC,but Incorrect for ARM-hisiv100-linux-gcc */
static void base64_encode(char *dbuf, char *sbuf, int len)
{
	struct data6 *ddd = NULL;
	int i = 0;
	char *tmp = NULL;
	char cc = '\0';
	for(i = 1; i <= len/3; i++)
	{
		tmp = sbuf+(i-1)*3;
		cc = tmp[2];
		tmp[2] = tmp[0];
		tmp[0] = cc;
		ddd = (struct data6 *)tmp;
		dbuf[(i-1)*4+0] = con628((unsigned int)ddd->d1);
		dbuf[(i-1)*4+1] = con628((unsigned int)ddd->d2);
		dbuf[(i-1)*4+2] = con628((unsigned int)ddd->d3);
		dbuf[(i-1)*4+3] = con628((unsigned int)ddd->d4);
	}
	if(len%3 == 1)
	{
		tmp = sbuf+(i-1)*3;
		cc = tmp[2];
		tmp[2] = tmp[0];
		tmp[0] = cc;
		ddd = (struct data6 *)tmp;
		dbuf[(i-1)*4+0] = con628((unsigned int)ddd->d1);
		dbuf[(i-1)*4+1] = con628((unsigned int)ddd->d2);
		dbuf[(i-1)*4+2] = '=';
		dbuf[(i-1)*4+3] = '=';
	}
	if(len%3 == 2)
	{
		tmp = sbuf+(i-1)*3;
		cc = tmp[2];
		tmp[2] = tmp[0];
		tmp[0] = cc;
		ddd = (struct data6 *)tmp;
		dbuf[(i-1)*4+0] = con628((unsigned int)ddd->d1);
		dbuf[(i-1)*4+1] = con628((unsigned int)ddd->d2);
		dbuf[(i-1)*4+2] = con628((unsigned int)ddd->d3);
		dbuf[(i-1)*4+3] = '=';
	}
	return;
}

/* open tcp socket */
static int open_socket(struct sockaddr *addr)
{
	int sockfd = 0;
	struct timeval timeout;
	timeout.tv_sec = 120;
	timeout.tv_usec = 0;
	sockfd=socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0)
	{
		SLOG(ERR,"Open sockfd(TCP) error!\n");
		return -1;
	}
	if(setsockopt(sockfd,SOL_SOCKET,SO_SNDTIMEO,&timeout,sizeof(timeout))<0)
	{
		SLOG(ERR,"setsockopt send timeout fail!\n");
		return -1;
	}
	if(setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,&timeout,sizeof(timeout))<0)
	{
		SLOG(ERR,"setsockopt recv timeout fail!\n");
		return -1;
	}
	if(connect(sockfd, addr, sizeof(struct sockaddr)) < 0)
	{
		SLOG(ERR,"Connect sockfd(TCP) error:%s!\n",strerror(errno));
		return -1;
	}
	return sockfd;
} 
static int getaddrinfo_proc(const char *host,const char *serv,struct sockaddr *servaddr)
{
	struct addrinfo hints, *res;
	int ret = 0;

	memset(&hints,0,sizeof(hints));
	//hints.ai_flags = AI_CANONNAME;
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;

	res_init();
	ret = getaddrinfo(host,serv,&hints,&res);
	if(ret)
	{
		fprintf(stderr,"getaddrinfo fail for host %s:%s\n",host,gai_strerror(ret));
		if(ret==EAI_SYSTEM)
			fprintf(stderr,"\terror:%s\n",strerror(errno));
		//res_close();
		return -1;
	}
	else
	{
		struct addrinfo *ptr;
		//struct sockaddr_in *servaddr;
		//char str[32]={0};

		//fprintf(stdout,"canonname : %s\n",res->ai_canonname);
		for(ptr=res;ptr;ptr=ptr->ai_next)
		{
			memcpy(servaddr,ptr->ai_addr,sizeof(struct sockaddr));
			break;
			//servaddr = (struct sockaddr_in*)ptr->ai_addr;
			//fprintf(stdout,"\taddress: %s\n",
			//		inet_ntop(ptr->ai_family,&servaddr->sin_addr,str,sizeof(str)));
		}
		freeaddrinfo(res);
	}
	//res_close();
	return 0;
}

// 发送邮件
static int sendemail(struct mail_meta *mail)
{
	int ret = 0;
	int sockfd = -1;
	struct sockaddr_in their_addr = {0};
	char buf[1500] = {0};
	char rbuf[1500] = {0};
	char login[128] = {0};
	char pass[128] = {0};

	memset(&their_addr, 0, sizeof(their_addr));
#if 0
	their_addr.sin_family = AF_INET;
	their_addr.sin_port = htons(25);
	their_addr.sin_addr.s_addr = inet_addr("112.90.141.71");//qq smtp 服务器
#endif
	if(getaddrinfo_proc(mail->smtp_server,"25"/*mail->port*/,(struct sockaddr*)&their_addr)<0)
	{
		SLOG(ERR,"getaddrinfo_proc fail!\n");
		ret = -1;
		goto end_sendemail;
	}
	sockfd = open_socket((struct sockaddr *)&their_addr);
	if(sockfd < 0)
	{
		ret = -1;
		goto end_sendemail;
	}

	SMTP_RECV(sockfd,rbuf,sizeof(rbuf));

	// EHLO
	SMTP_SEND(sockfd,buf,sizeof(buf),"EHLO %s\r\n",mail->smtp_server);
	SMTP_RECV(sockfd,rbuf,sizeof(rbuf));

	// AUTH LOGIN
	SMTP_SEND(sockfd,buf,sizeof(buf),"AUTH LOGIN\r\n");
	SMTP_RECV(sockfd,rbuf,sizeof(rbuf));

	// USER
	char *ptr_at = NULL; /* position of @ character */
	memset(buf, 0, 1500);
	strcpy(buf,mail->from);
	if((ptr_at=strchr(buf,'@'))!=NULL)
		*ptr_at = 0;
	memset(login, 0, 128);
	base64(login, buf, strlen(buf));
	SMTP_SEND(sockfd,buf,sizeof(buf),"%s\r\n", login);
	SMTP_RECV(sockfd,rbuf,sizeof(rbuf));

	// PASSWORD
	memset(buf, 0, 1500);
	strcpy(buf,mail->code);
	memset(pass, 0, 128);
	base64(pass, buf, strlen(buf));
	SMTP_SEND(sockfd,buf,sizeof(buf),"%s\r\n", pass);
	SMTP_RECV(sockfd,rbuf,sizeof(rbuf));

	// MAIL FROM
	SMTP_SEND(sockfd,buf,sizeof(buf), "MAIL FROM: %s\r\n",mail->from);
	SMTP_RECV(sockfd,rbuf,sizeof(rbuf));

	// RCPT TO 第一个收件人
	SMTP_SEND(sockfd,buf,sizeof(buf),"RCPT TO:<%s>\r\n", mail->to);
	SMTP_RECV(sockfd,rbuf,sizeof(rbuf));

	// DATA 准备开始发送邮件内容
	SMTP_SEND(sockfd,buf,sizeof(buf),"DATA\r\n");
	SMTP_RECV(sockfd,rbuf,sizeof(rbuf));

	// 发送邮件内容，\r\n.\r\n内容结束标记
	if(mail->body!=NULL)
	{// send attach use MIME
		if(send(sockfd,mail->body,strlen(mail->body),MSG_NOSIGNAL)!=strlen(mail->body))
		{
			SLOG(ERR,"send body not ok!\n");
		}
		free(mail->body);
		mail->body = NULL;
		SMTP_SEND(sockfd,buf,sizeof(buf),"\r\n.\r\n");
	}
	else
	{
		SMTP_SEND(sockfd,buf,sizeof(buf),"%s\r\n.\r\n", mail->info);
	}
	SMTP_RECV(sockfd,rbuf,sizeof(rbuf));

	// QUIT
	SMTP_SEND(sockfd,buf,sizeof(buf),"QUIT\r\n");
	SMTP_RECV(sockfd,rbuf,sizeof(rbuf));

end_sendemail:
	if(mail->body)
	{
		free(mail->body);
		mail->body = NULL;
	}
	if(sockfd>0)
		close(sockfd);

	return ret;
}

static int build_mime_body(struct mail_meta *mail)
{
	int ret = 0;

	if(mail==NULL)
		return -1;
	
	struct stat attach_stat;
	unsigned int attach_size = 0;
	memset(&attach_stat,0,sizeof(attach_stat));

	if(strlen(mail->attach)>0)
	{
		if(stat(mail->attach,&attach_stat))
		{
			SLOG(ERR,"stat attach <%s> fail:%s\n",mail->attach,strerror(errno));
			return -1;
		}
		else if(S_ISREG(attach_stat.st_mode))
		{
			attach_size = attach_stat.st_size*2/*base64 encode*/;
		}
		else
		{
			SLOG(ERR,"attach <%s> isnot a regular file!\n",mail->attach);
			return -1;
		}
		SLOG(PRI,"+++++++++attach :%s,size = %uKB\n",mail->attach,attach_stat.st_size>>10);
	}
	unsigned int size = attach_size + strlen(mail->info) + 2048;
	mail->body = (char *)malloc(size);
	if(mail->body == NULL)
	{
		SLOG(ERR,"malloc <%u> fail!\n",size);
		return -1;
	}
	/* clear to 0 ,because we would use base64 encode */
	memset(mail->body,0,size);
	// MIME body
	int body_len = 0;
	/* you should clear to 0,as base64-api doesnot set '\0' end-flag. */
	char subject[1024]={0},info[1024]={0};// base64 for subject,info
	snprintf(subject,sizeof(subject),"=?utf-8?B?");
	base64(subject+strlen(subject),mail->subject,strlen(mail->subject));
	/* you should ensure after base64 @subject@ is a string.*/
	strcat(subject,"?=");
	base64(info,mail->info,strlen(mail->info));

	//邮件头部信息
	/* if has many receiver, the format is like:
	 *   TO: <user1@qq.com>,<user2@qq.com>
	 */
	body_len = snprintf(mail->body,size-body_len,"From: <%s>\r\nTo: <%s>\r\nSubject: %s\r\n",
			(char*)mail->from,(char*)mail->to,(char*)subject/*mail->subject*/); //邮件
	body_len += snprintf(mail->body+body_len,size-body_len,"MIME-Version:1.0\r\n");
	body_len += snprintf(mail->body+body_len,size-body_len,
			"Content-Type:multipart/mixed;boundary=\"#BOUNDARY#\"\r\n\r\n");	//设置边界标识
	//body_len += snprintf(mail->body+body_len,size-body_len,"Content-Transfer-Encoding:7bit\r\n\r\n");
	//body_len += snprintf(mail->body+body_len,size-body_len,"%s\r\n\r\n",mail->info);
	
	//邮件内容头部信息
	body_len += snprintf(mail->body+body_len,size-body_len,"--#BOUNDARY#\r\n");
	body_len += snprintf(mail->body+body_len,size-body_len,
			"Content-Type: text/plain;charset=\"utf-8\"\r\n");
	body_len += snprintf(mail->body+body_len,size-body_len,"Content-Transfer-Encoding:base64\r\n\r\n");
	//邮件的内容部分
	body_len += snprintf(mail->body+body_len,size-body_len,"%s!\r\n",info/*mail->info*/);

	//附件头部信息
	if(attach_size > 0)
	{
		char fname[128]={0},*ptr = NULL;
		if((ptr=strrchr(mail->attach,'/'))!=NULL)
			strncpy(fname,ptr+1,sizeof(fname));
		else
			strncpy(fname,mail->attach,sizeof(fname));
		body_len += snprintf(mail->body+body_len,size-body_len,"--#BOUNDARY#\r\n");
		body_len += snprintf(mail->body+body_len,size-body_len,"Content-Type:image/jpg;name=%s\r\n",fname);
		body_len += snprintf(mail->body+body_len,size-body_len,"Content-Transfer-Encoding:base64\r\n");
		body_len += snprintf(mail->body+body_len,size-body_len,"Content-Disposition:attachment;filename=\"%s\"\r\n\r\n",fname);
		//fprintf(stdout,"[MIME Body]%s\n",mail->body);

		//用二进制方式读取附件文件内容并转为base64格式
		FILE* pf = fopen(mail->attach,"rb");
		if(NULL==pf)
		{
			SLOG(ERR,"open attach file read fail!\n");
			ret = -1;
			goto end_build;
		}
		char* filebuf = (char*) malloc(attach_stat.st_size);
		int nread = fread(filebuf,1,attach_stat.st_size,pf);
		if(nread != attach_stat.st_size)
		{
			SLOG(ERR,"read len[%d] != size[%u]\n",nread,attach_stat.st_size);
			free(filebuf);
			fclose(pf);
			ret = -1;
			goto end_build;
		}
		base64(mail->body+body_len,filebuf,attach_stat.st_size);
		free(filebuf);
		fclose(pf);
		//fprintf(stdout,"[MIME Body]%s\n",mail->body);
		//fprintf(stdout,"[Attach]%s\n",mail->body+body_len);
	}
	
end_build:
	if(ret<0)
	{
		if(mail->body)
		{
			free(mail->body);
			mail->body = NULL;
		}
	}
	return ret;
}

static int get_item_value(const char *prompt,char *value)
{
    char line[1024]={0};
    fprintf(stdout,"\t%s: ",prompt);
    fgets(line,sizeof(line),stdin);
    line[strlen(line)-1]=0;
    strcpy(value,line);
    return 0;
}

static int input_email_meta(struct mail_meta *mail)
{
	if(mail==NULL)
	{
		SLOG(ERR,"mail meta is null!\n");
		return -1;
	}

	memset(mail,0,sizeof(struct mail_meta));
#if 1
//	strcpy(mail->from,"ipcamere003@zmvideo.net");
//	strcpy(mail->code,"yan123456");
//	strcpy(mail->to,"2355639233@qq.com");
//	strcpy(mail->smtp_server,"l3.zmvideo.net");

	strcpy(mail->from,"ipc@ialarm911.net");
	strcpy(mail->code,"dubhe123");
	strcpy(mail->to,"hrj_lzh@163.com");
	strcpy(mail->smtp_server,"ialarm911.net");
	// ASCII English
	//strcpy(mail->subject,"Linux SMTP Agent Test");
	//strcpy(mail->info,"Bruce Test For Private SMTP");
	// UTF-8 Chinese
	strcpy(mail->subject,"Linux SMTP代理测试");
	strcpy(mail->info,"测试私有的简单SMTP代理");
	strcpy(mail->attach,"zlog.h");
	//strcpy(mail->attach,"/tmpfs/alarm_picture.jpg");
#else
	get_item_value("From",mail->from);
	get_item_value("Code",mail->code);
	get_item_value("To",mail->to);
	get_item_value("SMTP",mail->smtp_server);
	get_item_value("Subject",mail->subject);
	get_item_value("Info",mail->info);
	get_item_value("AttachFile",mail->attach);
#endif

	/** check parameters */
	if(strlen(mail->from)==0)
		return -1;
	if(strlen(mail->code)==0)
		return -1;
	if(strlen(mail->to)==0)
		return -1;
	if(strlen(mail->smtp_server)==0)
		return -1;

	mail->body = NULL; 
	if(strlen(mail->attach) && access(mail->attach,F_OK))
	{
		SLOG(ERR,"attach <%s> doesnot exist,please check!\n",mail->attach);
		return -1;
	}
	else if(build_mime_body(mail)<0)
	{// Build MIME body according to attach file 
		SLOG(ERR,"build_mime_body fail!\n");
		return -1;
	}
	/* remember to free mail->body memory!!! */
	mail->smtp_port = 25; // default
	fprintf(stdout,"Now Sending Email...\n");

	return 0;
}

int main(int argc, char *argv[])
{
	int ret = 0;

	struct mail_meta meta;
	if(input_email_meta(&meta)<0)
	{
		SLOG(ERR,"input_email_meta fail!\n");
		exit(EXIT_FAILURE);
	}

	if(sendemail(&meta)<0)
	{
		SLOG(ERR,"sendemail fail!\n");
		exit(EXIT_FAILURE);
	}
	return ret;
}

